/*
 * PainterUtil.java
 *
 * Created on July 20, 2006, 1:18 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.jdesktop.beans.editors;

import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.LinearGradientPaint;
import java.awt.RadialGradientPaint;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Area;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.QuadCurve2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.beans.BeanInfo;
import java.beans.DefaultPersistenceDelegate;
import java.beans.Encoder;
import java.beans.ExceptionListener;
import java.beans.Expression;
import java.beans.Introspector;
import java.beans.PersistenceDelegate;
import java.beans.PropertyDescriptor;
import java.beans.Statement;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;

import javax.imageio.ImageIO;
import javax.swing.JComponent;

import org.jdesktop.beans.editors.PainterPropertyEditor.AffineTransformDelegate;
import org.jdesktop.beans.editors.PainterPropertyEditor.Arc2DDelegate;
import org.jdesktop.beans.editors.PainterPropertyEditor.AreaDelegate;
import org.jdesktop.beans.editors.PainterPropertyEditor.CubicCurve2DDelegate;
import org.jdesktop.beans.editors.PainterPropertyEditor.Ellipse2DDelegate;
import org.jdesktop.beans.editors.PainterPropertyEditor.GeneralPathDelegate;
import org.jdesktop.beans.editors.PainterPropertyEditor.GradientPaintDelegate;
import org.jdesktop.beans.editors.PainterPropertyEditor.InsetsDelegate;
import org.jdesktop.beans.editors.PainterPropertyEditor.Line2DDelegate;
import org.jdesktop.beans.editors.PainterPropertyEditor.LinearGradientPaintDelegate;
import org.jdesktop.beans.editors.PainterPropertyEditor.Point2DDelegate;
import org.jdesktop.beans.editors.PainterPropertyEditor.QuadCurve2DDelegate;
import org.jdesktop.beans.editors.PainterPropertyEditor.RadialGradientPaintDelegate;
import org.jdesktop.beans.editors.PainterPropertyEditor.Rectangle2DDelegate;
import org.jdesktop.beans.editors.PainterPropertyEditor.RoundRectangle2DDelegate;
import org.jdesktop.swingx.JXButton;
import org.jdesktop.swingx.JXLabel;
import org.jdesktop.swingx.JXPanel;
import org.jdesktop.swingx.painter.AbstractLayoutPainter;
import org.jdesktop.swingx.painter.AbstractPainter;
import org.jdesktop.swingx.painter.CompoundPainter;
import org.jdesktop.swingx.painter.ImagePainter;
import org.jdesktop.swingx.painter.Painter;
import org.jdesktop.swingx.painter.RectanglePainter;

/**
 *
 * @author joshy
 */
public class PainterUtil {

	/** Creates a new instance of PainterUtil */
	private PainterUtil() {
	}

	/*
	 * public static void main(String[] args) throws Exception { ImagePainter ip
	 * = new ImagePainter();
	 * ip.setImageString("file:/Users/joshy/Pictures/cooltikis.jpg"); File
	 * outfile = new File("/Users/joshy/Desktop/test.xml");
	 * outfile.createNewFile(); p("outfile = " + outfile.getAbsolutePath());
	 * p("exists = " + outfile.exists()); savePainterToFile(ip, outfile,
	 * outfile.getParentFile().toURL()); p("---------"); ip =
	 * (ImagePainter)loadPainter(outfile); p("image = " + ip.getImage()); ip =
	 * (ImagePainter)loadPainter(outfile.toURL()); p("image = " +
	 * ip.getImage()); p("==================");
	 * 
	 * 
	 * }
	 */

	public static Painter loadPainter(File file) throws FileNotFoundException, MalformedURLException, IOException {
		return loadPainter(file.toURI().toURL(), file.toURI().toURL());
	}

	private static Painter loadPainter(final URL in, URL baseURL) throws FileNotFoundException, IOException {
		Thread.currentThread().setContextClassLoader(PainterUtil.class.getClassLoader());
		XMLDecoder dec = new XMLDecoder(in.openStream());
		// p("creating a persistence owner with the base url: " + baseURL);
		dec.setOwner(new PersistenceOwner(baseURL));
		dec.setExceptionListener(new ExceptionListener() {
			public void exceptionThrown(Exception ex) {
				System.out.println(ex.getMessage());
				ex.printStackTrace();
			}
		});
		Object obj = dec.readObject();
		return (Painter) obj;
	}

	public static Painter loadPainter(URL url) throws IOException {
		return loadPainter(url, url);
	}

	static public void savePainterToFile(Painter compoundPainter, File file) throws IOException {
		savePainterToFile(compoundPainter, file, file.getParentFile().toURI().toURL());
	}

	static public void savePainterToFile(Painter compoundPainter, File file, URL baseURL) throws IOException {
		// System.setErr(null);
		// u.p("writing out to: " + file.getCanonicalPath());
		setTransient(ImagePainter.class, "image");
		setTransient(ImagePainter.class, "imageString");
		// setTransient(CompoundPainter.class,"antialiasing");
		// setTransient(AbstractPainter.class,"antialiasing");
		// setTransient(AbstractPainter.class,"renderingHints");
		// setPropertyDelegate();

		XMLEncoder e = new XMLEncoder(new FileOutputStream(file));
		e.setOwner(new PersistenceOwner(baseURL));
		// p("owner = " + e.getOwner());
		// e.setOwner(compoundPainter);

		// serialize the enums
		// e.setPersistenceDelegate(AbstractPainter.Antialiasing.class, new
		// TypeSafeEnumPersistenceDelegate());
		e.setPersistenceDelegate(AbstractPainter.Interpolation.class, new TypeSafeEnumPersistenceDelegate());
		// e.setPersistenceDelegate(AbstractPainter.FractionalMetrics.class, new
		// TypeSafeEnumPersistenceDelegate());
		e.setPersistenceDelegate(RectanglePainter.Style.class, new TypeSafeEnumPersistenceDelegate());
		e.setPersistenceDelegate(AbstractLayoutPainter.HorizontalAlignment.class, new TypeSafeEnumPersistenceDelegate());
		e.setPersistenceDelegate(AbstractLayoutPainter.VerticalAlignment.class, new TypeSafeEnumPersistenceDelegate());

		e.setPersistenceDelegate(AbstractPainter.class, new AbstractPainterDelegate());
		e.setPersistenceDelegate(ImagePainter.class, new ImagePainterDelegate());
		e.setPersistenceDelegate(RenderingHints.class, new RenderingHintsDelegate());
		e.setPersistenceDelegate(GradientPaint.class, new GradientPaintDelegate());
		e.setPersistenceDelegate(Arc2D.Float.class, new Arc2DDelegate());
		e.setPersistenceDelegate(Arc2D.Double.class, new Arc2DDelegate());
		e.setPersistenceDelegate(CubicCurve2D.Float.class, new CubicCurve2DDelegate());
		e.setPersistenceDelegate(CubicCurve2D.Double.class, new CubicCurve2DDelegate());
		e.setPersistenceDelegate(Ellipse2D.Float.class, new Ellipse2DDelegate());
		e.setPersistenceDelegate(Ellipse2D.Double.class, new Ellipse2DDelegate());
		e.setPersistenceDelegate(Line2D.Float.class, new Line2DDelegate());
		e.setPersistenceDelegate(Line2D.Double.class, new Line2DDelegate());
		e.setPersistenceDelegate(Point2D.Float.class, new Point2DDelegate());
		e.setPersistenceDelegate(Point2D.Double.class, new Point2DDelegate());
		e.setPersistenceDelegate(QuadCurve2D.Float.class, new QuadCurve2DDelegate());
		e.setPersistenceDelegate(QuadCurve2D.Double.class, new QuadCurve2DDelegate());
		e.setPersistenceDelegate(Rectangle2D.Float.class, new Rectangle2DDelegate());
		e.setPersistenceDelegate(Rectangle2D.Double.class, new Rectangle2DDelegate());
		e.setPersistenceDelegate(RoundRectangle2D.Float.class, new RoundRectangle2DDelegate());
		e.setPersistenceDelegate(RoundRectangle2D.Double.class, new RoundRectangle2DDelegate());
		e.setPersistenceDelegate(Area.class, new AreaDelegate());
		e.setPersistenceDelegate(GeneralPath.class, new GeneralPathDelegate());
		e.setPersistenceDelegate(AffineTransform.class, new AffineTransformDelegate());
		e.setPersistenceDelegate(RadialGradientPaint.class, new RadialGradientPaintDelegate());
		e.setPersistenceDelegate(LinearGradientPaint.class, new LinearGradientPaintDelegate());
		e.setPersistenceDelegate(Insets.class, new InsetsDelegate());

		e.writeObject(compoundPainter);
		e.close();
	}

	// private static void setPropertyDelegate(Class clazz, String property, )
	private static void setTransient(Class clazz, String property) {
		try {
			BeanInfo info = Introspector.getBeanInfo(clazz);
			PropertyDescriptor[] propertyDescriptors = info.getPropertyDescriptors();
			for (int i = 0; i < propertyDescriptors.length; ++i) {
				PropertyDescriptor pd = propertyDescriptors[i];
				if (pd.getName().equals(property)) {
					pd.setValue("transient", Boolean.TRUE);
					// u.p(pd.attributeNames());
				}
			}
		} catch (Exception ex) {
			// u.p(ex);
		}
	}

	static class TypeSafeEnumPersistenceDelegate extends PersistenceDelegate {
		@Override
		protected boolean mutatesTo(Object oldInstance, Object newInstance) {
			return oldInstance == newInstance;
		}

		@Override
		protected Expression instantiate(Object oldInstance, Encoder out) {
			Class type = oldInstance.getClass();
			if (!Modifier.isPublic(type.getModifiers()))
				throw new IllegalArgumentException("Could not instantiate instance of non-public class: " + oldInstance);

			for (Field field : type.getFields()) {
				int mod = field.getModifiers();
				if (Modifier.isPublic(mod) && Modifier.isStatic(mod) && Modifier.isFinal(mod)
						&& (type == field.getDeclaringClass())) {
					try {
						if (oldInstance == field.get(null))
							return new Expression(oldInstance, field, "get", new Object[] { null });
					} catch (IllegalAccessException exception) {
						throw new IllegalArgumentException("Could not get value of the field: " + field, exception);
					}
				}
			}
			throw new IllegalArgumentException("Could not instantiate value: " + oldInstance);
		}
	}

	public static final class RenderingHintsDelegate extends PersistenceDelegate {
		@Override
		protected Expression instantiate(Object oldInstance, Encoder out) {
			// u.p("rh inst");
			// give it a constructor w/ null as the argument
			return new Expression(oldInstance, oldInstance.getClass(), "new", new Object[] { null });
		}

		@Override
		protected void initialize(Class<?> type, Object oldInstance, Object newInstance, Encoder out) {
			// u.p("rh init ");
			RenderingHints rh = (RenderingHints) oldInstance;
			out.writeStatement(new Statement(oldInstance, "put", new Object[] { RenderingHints.KEY_ANTIALIASING,
					RenderingHints.VALUE_ANTIALIAS_ON }));
			// u.p("done");
		}
	}

	public static final class AbstractPainterDelegate extends DefaultPersistenceDelegate {
		@Override
		protected void initialize(Class type, Object oldInstance, Object newInstance, Encoder out) {
			// p("ap delegate called");
			super.initialize(type, oldInstance, newInstance, out);
		}
	}

	public static final class ImagePainterDelegate extends DefaultPersistenceDelegate {
		@Override
		protected void initialize(Class type, Object oldInstance, Object newInstance, Encoder out) {
			// p("image painter delegate called");
			super.initialize(type, oldInstance, newInstance, out);
			// p("old instance = " + oldInstance);
			// p("owner = " + ((XMLEncoder)out).getOwner());
			PersistenceOwner owner = (PersistenceOwner) ((XMLEncoder) out).getOwner();
			ImagePainter ip = (ImagePainter) oldInstance;
			// p("need to convert string: " + ip.getImageString());
			// String s = owner.toXMLURL(ip.getImageString());
			// p("converted to: " + s);
			// out.writeExpression(new
			// Expression(oldInstance,owner,"fromXMLURL",new
			// Object[]{ip.getImageString()}));
			// out.writeStatement(new Statement(owner,"fromXMLURL",new
			// Object[]{ip.getImageString()}));
			// out.writeStatement(new Statement(oldInstance,"setImageString",new
			// Object[]{
			// new Expression(oldInstance,owner,"fromXMLURL",new
			// Object[]{ip.getImageString()})
			// }));

			out.writeStatement(new Statement(oldInstance, "setResolver", new Object[] { owner }));
			// out.writeStatement(new Statement(oldInstance,"setImageString",new
			// Object[]{s}));
		}
	}

	public static void savePainterToImage(JComponent testPanel, CompoundPainter compoundPainter, File file) throws IOException {
		BufferedImage img = new BufferedImage(testPanel.getWidth(), testPanel.getHeight(), BufferedImage.TYPE_INT_ARGB);
		Graphics2D g = img.createGraphics();
		setBGP(testPanel, compoundPainter);
		testPanel.paint(g);
		ImageIO.write(img, "png", file);
	}

	public static void setBGP(JComponent comp, Painter painter) {
		if (comp instanceof JXPanel) {
			((JXPanel) comp).setBackgroundPainter(painter);
		}
		if (comp instanceof JXButton) {
			((JXButton) comp).setBackgroundPainter(painter);
		}
	}

	public static void setFGP(JComponent comp, Painter painter) {
		if (comp instanceof JXLabel) {
			((JXLabel) comp).setForegroundPainter(painter);
		}
		if (comp instanceof JXButton) {
			((JXButton) comp).setForegroundPainter(painter);
		}
	}

	public static Painter getFGP(JComponent comp) {
		if (comp instanceof JXLabel) {
			return ((JXLabel) comp).getForegroundPainter();
		}
		if (comp instanceof JXButton) {
			return ((JXButton) comp).getForegroundPainter();
		}
		return null;
	}

	public static Painter getBGP(JComponent comp) {
		if (comp instanceof JXPanel) {
			return ((JXPanel) comp).getBackgroundPainter();
		}
		if (comp instanceof JXButton) {
			return ((JXButton) comp).getBackgroundPainter();
		}
		return null;
	}

	public static class PersistenceOwner {
		private URL baseURL;

		public PersistenceOwner(URL baseURL) {
			this.baseURL = baseURL;
		}

		public URL getBaseURL() {
			return baseURL;
		}

		public String toXMLURL(String url) {
			// p("========");
			// p("in toXMLURL()");
			// p("base url = " + baseURL);
			// p("url to convert = " + url);
			// trim off the beginning if the url is relative to the base
			if (url.startsWith(baseURL.toString())) {
				// p("it's a valid substring!!!!!!!!!!!!");
				url = url.substring(baseURL.toString().length());
				// p("subsstring = " + url);
			}
			// p("final url = " + url);
			// p("========");
			return url;
		}

		public String fromXMLURL(String url) throws MalformedURLException {
			/*
			 * p("========"); p("in fromXMLURL()"); p("base url = " + baseURL);
			 * p("url to convert: " + url);
			 */
			String s = new URL(baseURL, url).toString();
			// p("returning: " + s);
			// p("========");
			return s;
		}
	}
}
